package groep1.graph;

import groep1.graph.graphstrategy.layout.GraphPlacementStrategy;
import groep1.graph.graphstrategy.line.Line;
import groep1.graph.graphstrategy.line.LineStrategy;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import javax.swing.JPanel;

import bluej.debugmgr.inspector.FieldInfo;

/**
 * The component which actually draws the graph objects.
 * 
 * @author Niels Billen, Jorn Van Loock
 * @version 0.1
 * @since 0.1
 */
public class GraphComponent extends JPanel implements GraphMediator,
		MouseListener, MouseMotionListener {
	/**
	 * Creates a new GraphComponent to draw the objects on the screen placed by
	 * a given placement strategy.
	 * 
	 * @param placementStrategy
	 *            The strategy to place the objects on the screen.
	 * @param lineStrategy
	 *            The strategy to draw lines on the screen.
	 * @throws NullPointerException
	 *             When one of the given strategies is null.
	 */
	public GraphComponent(GraphPlacementStrategy placementStrategy,
			LineStrategy lineStrategy) throws NullPointerException {
		super(true);
		
		setGraphPlacementStrategy(placementStrategy);
		setLineStrategy(lineStrategy);
		setBackground(Color.WHITE);

		// addTestObjects();

		addMouseListener(this);
		addMouseMotionListener(this);
	}

	/**
	 * Sets the objects to display for this GraphComponent.
	 * 
	 * @param objects
	 *            The objects to display.
	 * @throws NullPointerException
	 *             When the given objects are null.
	 */
	public void setDisplayObjects(HashMap<String, List<FieldInfo>> objects) {
		if (objects == null)
			throw new NullPointerException("The given objects are null!");

		List<GraphObject> objectsToAdd = new ArrayList<GraphObject>();

		Set<String> keys = objects.keySet();
		for (String temp : keys) {
			objectsToAdd.add(new GraphObject(temp, objects.get(temp)));
		}
		setGraphObjects(objectsToAdd);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JComponent#update(java.awt.Graphics)
	 */
	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);

		g.drawImage(buffer, 0, 0, null);

		buffer = new BufferedImage(getWidth(), getHeight(),
				BufferedImage.TYPE_INT_ARGB);
		Graphics g2 = buffer.getGraphics();

		g2.setColor(Color.WHITE);
		g2.fillRect(0, 0, getWidth() - 1, getHeight() - 1);

		for (GraphObject object : objects)
			object.draw(g2, scroll.x, scroll.y);

		for (Line line : getLineStrategy().getLines())
			line.draw(g2, scroll.x, scroll.y, Color.BLACK, false);

		Point p1 = new Point(mousePoint.x - scroll.x, mousePoint.y - scroll.y);
		Point p2 = new Point(p1.x - 1, p1.y);
		Point p3 = new Point(p1.x + 1, p1.y);
		Point p4 = new Point(p1.x, p1.y - 1);
		Point p5 = new Point(p1.x, p1.y + 1);

		for (Line line : getLineStrategy().getLines())
			if (line.overlap(mousePoint) || line.overlap(p1)
					|| line.overlap(p2) || line.overlap(p3) || line.overlap(p4)
					|| line.overlap(p5))
				line.draw(g2, scroll.x, scroll.y, Color.RED, true);

		g.drawImage(buffer, 0, 0, null);

		g2.dispose();
	}

	private BufferedImage buffer;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * groep1.graph.GraphMediator#setGraphPlacementStrategy(groep1.graph.layout
	 * .GraphPlacementStrategy)
	 */
	@Override
	public void setGraphPlacementStrategy(GraphPlacementStrategy strategy) {
		if (strategy == null)
			throw new NullPointerException("The given strategy is null!");
		this.placementStrategy = strategy;
		this.placementStrategy.setMediator(this);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see groep1.graph.GraphMediator#getGraphPlacementStrategy()
	 */
	@Override
	public GraphPlacementStrategy getGraphPlacementStrategy() {
		return placementStrategy;
	}

	/**
	 * The layout strategy for placing the objects on the screen.
	 */
	private GraphPlacementStrategy placementStrategy;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * groep1.graph.GraphMediator#setLineStrategy(groep1.graph.line.LineStrategy
	 * )
	 */
	@Override
	public void setLineStrategy(LineStrategy lineStrategy)
			throws NullPointerException {
		if (lineStrategy == null)
			throw new NullPointerException("The given strategy is null!");
		this.lineStrategy = lineStrategy;
		this.lineStrategy.setMediator(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see groep1.graph.GraphMediator#getLineStrategy()
	 */
	@Override
	public LineStrategy getLineStrategy() {
		return lineStrategy;
	}

	/**
	 * The strategy for placing the lines on the screen.
	 */
	private LineStrategy lineStrategy;

	/**
	 * Layouts the given objects.
	 * 
	 * @param objects
	 *            The objects to layout.
	 * @throws NullPointerException
	 *             When the given objects are null.
	 */
	public void layout(Collection<GraphObject> objects)
			throws NullPointerException {
		getGraphPlacementStrategy().layout(objects);
		getLineStrategy().connect(objects);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see groep1.graph.GraphMediator#relayout(int, int)
	 */
	@Override
	public void relayout(int addedHorizontalSeperation,
			int addedVerticalSeperation) {
		int hSeperation = getGraphPlacementStrategy()
				.getMinimumHorizontalSeperation() + addedHorizontalSeperation;
		int vSeperation = getGraphPlacementStrategy()
				.getMinimumVerticalSeperation() + addedVerticalSeperation;

		getGraphPlacementStrategy().setMinimumHorizontalSeperation(hSeperation);
		getGraphPlacementStrategy().setMinimumVerticalSeperation(vSeperation);

		getGraphPlacementStrategy().layout(objects);
		getLineStrategy().connect(objects);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see groep1.graph.GraphMediator#dimensionChanged(int, int)
	 */
	@Override
	public void dimensionChanged(int width, int height) {
		if (width > componentSize.width)
			componentSize.width = width;
		if (height > componentSize.height)
			componentSize.height = height;

		setPreferredSize(componentSize);
		revalidate();
		repaint();
	}

	/**
	 * The dimension of this component.
	 */
	private Dimension componentSize = new Dimension();

	/**
	 * Add's the given object to the objects to be layout.
	 * 
	 * @param object
	 *            The object to add.
	 * @throws NullPointerException
	 *             When the given object is null.
	 */
	protected void addGraphObject(GraphObject object) {
		if (object == null)
			throw new NullPointerException("The given object is null!");
		this.objects.add(object);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see groep1.graph.GraphMediator#setGraphObjects(java.util.Collection)
	 */
	@Override
	public void setGraphObjects(Collection<GraphObject> objects)
			throws NullPointerException {
		if (objects == null)
			throw new NullPointerException("The given object is null!");
		this.objects.clear();

		for (GraphObject object : objects)
			addGraphObject(object);

		layout(objects);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see groep1.graph.GraphMediator#getGraphObjects()
	 */
	@Override
	public Collection<GraphObject> getGraphObjects() {
		return new ArrayList<GraphObject>(objects);
	}

	/**
	 * The list with objects to layout.
	 */
	private List<GraphObject> objects = new ArrayList<GraphObject>();

	/**
	 * The amount of scroll.
	 */
	private Point scroll = new Point();

	/**
	 * An ID for serialization.
	 */
	private static final long serialVersionUID = 447846813518640990L;

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseClicked(MouseEvent e) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
	 */
	@Override
	public void mousePressed(MouseEvent e) {
		scrollAtClick = new Point(scroll);
		clickPoint = e.getPoint();
	}

	/**
	 * The last position where the mouse was down.
	 */
	private Point clickPoint = new Point();

	/**
	 * The scroll point when we last clicked.
	 */
	private Point scrollAtClick = new Point();

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseReleased(MouseEvent e) {
		scroll.x = scrollAtClick.x + e.getPoint().x - clickPoint.x;
		scroll.y = scrollAtClick.y + e.getPoint().y - clickPoint.y;
		update(getGraphics());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseEntered(MouseEvent e) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseExited(MouseEvent e) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent
	 * )
	 */
	@Override
	public void mouseDragged(MouseEvent e) {
		scroll.x = scrollAtClick.x + e.getPoint().x - clickPoint.x;
		scroll.y = scrollAtClick.y + e.getPoint().y - clickPoint.y;
		update(getGraphics());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseMoved(MouseEvent e) {
		mousePoint.setLocation(e.getPoint());

		revalidate();
		repaint();
	}

	/**
	 * The point of the mouse's last loacation.
	 */
	private Point mousePoint = new Point();
}
